package implementations.sm_kernel;

import interfaces.kernel.JCL_orb;
import interfaces.kernel.JCL_result;
import interfaces.kernel.JCL_skeleton;
import interfaces.kernel.JCL_task;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

import commom.JCL_resultImpl;
import commom.JCL_skeletonImpl;

public class JCL_orbImpl<T extends JCL_result> implements JCL_orb<T> {

	private Map<String, Class<?>> nameMap;
	private Map<String, Object> globalVars;
	private Set<String> locks;
		
	private JCL_orbImpl (){		
		nameMap = new ConcurrentHashMap<String, Class<?>>();
		locks = new ConcurrentSkipListSet<String>();
		globalVars = new ConcurrentHashMap<String, Object>();	
	}

	@Override
	public void execute(JCL_task task, Map<String, T> results) {
		try{
			T jResult = results.get(task.getTaskID());
			
			// reflection
			if(task.getObjectMethod()==null){
				Class<?> c;
				if((c= nameMap.get(task.getObjectName()))!=null){
					task.setObjectClass(c);
					JCL_skeleton jcr = JCL_skeletonImpl.getInstance();
					Object result = jcr.execute(task);
					if(result!=null)
						jResult.setCorrectResult(result);	
					else jResult.setCorrectResult("no result");
					
				}			
					
			}else{
				Class<?> c1 = nameMap.get(task.getObjectName());
				if(c1==null) c1= Class.forName(task.getObjectName());
				
				if(c1!=null){
					task.setObjectClass(c1);
					JCL_skeleton jcr = JCL_skeletonImpl.getInstance();
					Object result = jcr.execute(task);
					if(result!=null)						
						jResult.setCorrectResult(result);
					else jResult.setCorrectResult("no result");
					
				}
			}
			
			synchronized (jResult){
				jResult.notify();
			}
			
			long time = System.nanoTime() - task.getTaskTime();
			task.setTaskTime(time);
			
		}catch (Exception e){
			System.err.println("problem in JCL orb execute(JCL_task task, Map<String, T> results)");
			e.printStackTrace();
			T jResult = results.get(task.getTaskID());
			jResult.setErrorResult(e);
			
			synchronized (jResult){
				jResult.notify();
			}
			
			long time = System.nanoTime() - task.getTaskTime();
			task.setTaskTime(time);
		}
	}

	@Override
	public synchronized boolean register(Class<?> serviceClass, String nickName) {
		try{
			
			if(nameMap.containsKey(nickName))
				return false;
			else{
				nameMap.put(nickName, serviceClass);
				return true;
			}			
			
		} catch (Exception e){
			System.err.println("problem in JCL orb register(Class<?> serviceClass, String nickName)");
			
			return false;
		}
	}
	
	@Override
	public synchronized boolean unRegister(String nickName) {
		try{
						
			if(nameMap.containsKey(nickName)){
				nameMap.remove(nickName);
				return true;
			}else{
				return false;
			}						
			
		} catch (Exception e){
			System.err.println("problem in JCL orb unRegister(String nickName)");
			
			return false;
		}
	}
	
	
	@Override
	public synchronized boolean destroyGlobalVar(String varName) {
		try{
		
			if(globalVars.containsKey(varName)){
				globalVars.remove(varName);
				return true;
			}else return false;			
			
		}catch (Exception e){
			System.err.println("problem in JCL orb destroyGlobalVar(String varName)");
			
			return false;
		}
	}
	
	@Override
	public synchronized boolean instantiateGlobalVar(String varName, Object instance) {
		// TODO Auto-generated method stub
		try{
			if(instance==null)
				return false;
			
			if(!globalVars.containsKey(varName)){
				globalVars.put(varName, instance);
				return true;
			} else return false;
						
			
		}catch (Exception e){
			System.err.println("problem in JCL orb instantiateGlobalVar(String varName, Object instance)");
			
			return false;
		}
		
	}

	@Override
	public synchronized Object instantiateGlobalVar(String nickName, String varName, File[] jars,
			Object[] defaultVarValue) {
		// TODO Auto-generated method stub
				
		try{
			
			if(globalVars.get(nickName)!=null)
				return false;
			else{				
				
				if(defaultVarValue==null){
					Object var = registerJar(jars, varName).newInstance();
					globalVars.put(nickName, var);
					return true;
				}else{
					Constructor[] cs= registerJar(jars, varName).getConstructors();
					for(Constructor c:cs){
						if (c.getParameterTypes() != null){
							boolean flag=true;
							if(c.getParameterTypes().length==defaultVarValue.length)
								for(int i=0; i<c.getParameterTypes().length; i++){
									Class<?>aClass = c.getParameterTypes()[i];
									if(!aClass.equals(defaultVarValue[i].getClass())) flag=false;
													
								}
							if(flag ==true){
								Object var = c.newInstance(defaultVarValue);
								globalVars.put(nickName, var);
								return true;
								
							}
						}
					}
				}
								
				return false;
			}
			
		}catch (Exception e){
			System.err.println("problem in JCL orb instantiateGlobalVar(String varName, File[] jars, Object[] defaultVarValue)");
			
			return false;
		}
	}
	
	@Override
	public boolean setValue(String varName, Object value) {
		// TODO Auto-generated method stub
				
		try{
			if(!globalVars.containsKey(varName))
				return false;
			else{
				globalVars.put(varName, value);
				locks.remove(varName);
				return true;
			}
			
			
		}catch (Exception e){
			System.err.println("problem in JCL orb setValue(String varName, Object value)");
			
			return false;
		}			
	}
	
	@Override
	public JCL_result getValue(String varName) {
		try{
			Object obj = globalVars.get(varName);
			if(obj == null) {
				JCL_result jclr = new JCL_resultImpl();
				jclr.setCorrectResult(varName + " => No value found!");
				return jclr;				
			}else {
				JCL_result jclr = new JCL_resultImpl();
				jclr.setCorrectResult(obj);
				return jclr;
			}			
		}catch(Exception e){
			System.err.println("problem in JCL orb getValue(String varName)");
			
			JCL_result jclr = new JCL_resultImpl();
			jclr.setErrorResult(e);
			return jclr;
		}		
	}

	
	@Override
	public JCL_result getValueLocking(String varName) {
		// TODO Auto-generated method stub
		try{	
					
			if(globalVars.containsKey(varName)){
				synchronized (locks) {
					//no wait and notify
					while(locks.contains(varName));
											
					JCL_result jclr = new JCL_resultImpl();
					jclr.setCorrectResult(globalVars.get(varName));
					locks.add(varName);
				
					return jclr;
				}
				
			}else{
				
				JCL_result jclr = new JCL_resultImpl();
				//jclr.setCorrectResult(varName + " => No value found!");
				return jclr;
			}			
										
		}catch (Exception e){
			System.err.println("problem in JCL orb getValueLocking(String varName)");
			
			JCL_result jclr = new JCL_resultImpl();
			jclr.setErrorResult(e);
			return jclr;
		}
		
	}
	
	@Override
	public boolean setValueUnlocking(String varName, Object value) {
		// TODO Auto-generated method stub			
		try{
			
			if(globalVars.containsKey(varName)){
				globalVars.put(varName, value);
				locks.remove(varName);
								
				return true;
				
			}else {
				return false;
			}
			
		}catch (Exception e){
			System.err.println("problem in JCL orb setValueUnlocking(String varName)");
			
			return false;
		}
		
	}
	
	
	@Override
	public boolean register(File[] f, String classToBeExecuted) {
		try{
						
			if(containsTask(classToBeExecuted)==true)
				return true;
			else {
				Class<?> c= registerJar(f, classToBeExecuted);
				synchronized (c) {
					if(c!=null){
						return register(c, classToBeExecuted);
					}else {
						System.err.println(" not registered");					
						return false;
					}
				}
				
			}
			
		}catch(Exception e){
			
			System.err.println("problem in JCL orb register(File f, String classToBeExecuted)");
			e.printStackTrace();
			return false;
		}
	}
	
	private synchronized Class<?> registerJar(File[] fs, String classToBeExecuted) throws Exception{
		if(!new File("../user_jars/").isDirectory())
			new File("../user_jars/").mkdir();
		
		for(File f: fs)		
			copyJarFile(new JarFile(f), new File("../user_jars/"));
		/*}else{
			String[] files = new File("../user_jars/").list();
			boolean flag = false;
			for (String aux: files){
				if(aux.startsWith(fs[0].getName()))
					flag=true;
				//System.err.println(aux + " " + fs[0].getName());
			}
			if (flag==false)
				for(File f: fs)		
					copyJarFile(new JarFile(f), new File("../user_jars/"));
			
		}*/
		URL[] urls = new URL[fs.length];
		
		for(int i=0; i<fs.length; i++)						
				urls[i] = new File("../user_jars/" + fs[i].getName()).toURL(); 	
		
		JarFile jar = new JarFile("../user_jars/" + fs[0].getName());
		
	    for (Enumeration<JarEntry> entries = jar.entries() ; entries.hasMoreElements() ;)
	    {
	        JarEntry entry = entries.nextElement();
	        String file = entry.getName();
	        
	        if (file.endsWith(classToBeExecuted+ ".class"))
	        {
	           
	        	String classname = file.replace('/', '.').substring(0, file.length() - 6);
	        	
	        	
	        	Enumeration<URL> jars = ClassLoader.getSystemClassLoader().getResources(urls[0].toString());
			    
	        	if(jars.hasMoreElements()) System.err.println("used...");
	        	
	        	URLClassLoader urlCL = new URLClassLoader(urls);
	        	
	        	Class<?> c = urlCL.loadClass(classname);
			    
			    return c;
	        }
	    }	
	    
	    
	    return null;	
		
	}
	
	private static void copyJarFile(JarFile jarFile, File destDir) throws Exception {
	       String fileName = jarFile.getName();
	       
	       String fileNameLastPart = "";
	       
	       if(fileName.lastIndexOf(File.separator)==-1)
	    	   fileNameLastPart = fileName;
	       else fileNameLastPart = fileName.substring(fileName.lastIndexOf(File.separator));
	       
	       File destFile = new File(destDir, fileNameLastPart);
	 
	       JarOutputStream jos = new JarOutputStream(new FileOutputStream(destFile));
	       Enumeration<JarEntry> entries = jarFile.entries();
	 
	       while (entries.hasMoreElements()) {
	           JarEntry entry = entries.nextElement();
	           InputStream is = jarFile.getInputStream(entry);
	 
	           //jos.putNextEntry(entry);
	           //create a new entry to avoid ZipException: invalid entry compressed size
	           jos.putNextEntry(new JarEntry(entry.getName()));
	           byte[] buffer = new byte[4096];
	           int bytesRead = 0;
	           while ((bytesRead = is.read(buffer)) != -1) {
	               jos.write(buffer, 0, bytesRead);
	           }
	           is.close();
	           jos.flush();
	           jos.closeEntry();
	       }
	       jos.close();
	}
	
	@Override
	public boolean containsTask(String nickName) {
		return this.nameMap.containsKey(nickName);
	}

	@Override
	public boolean containsGlobalVar(String nickName) {
		return this.globalVars.containsKey(nickName);
	}

	
	@SuppressWarnings("rawtypes")
	public static JCL_orb getInstance(){
		return Holder.instance;
	}
	
	private static class Holder{
		@SuppressWarnings("rawtypes")
		private static final JCL_orb instance = new JCL_orbImpl();
	}
	

}
